#ifndef PGWB_H
#define PGWB_H

//write data in binary format into PostgreSQL
//all functions must be used inside 'COPY with binary' SQL command

#include <string>

#include <boost/cstdint.hpp>

#include "arra_size.h"
#include "swap_bytes.h"

using boost::int64_t;
using boost::int32_t;
using boost::uint32_t;
using boost::int16_t;

namespace PGWB //PostGresqlWriteBinary
{

namespace
{

template<typename Output>
void writeArray(Output * file, const char * array, int length)
{
    file->write(array, length);
}

template<typename Output, typename Data>
void writeInteger(Output * file, Data data)
{
    data = endian::swap_bytes<Data, sizeof(Data)>()(data);
    file->write(reinterpret_cast<const char*>(&data), sizeof(Data));
}

//using fake template, we dont need 'function mul1M undefined'
template<typename T>
int64_t mul1M(int32_t arg, T /*t*/)
{
    int64_t res = arg;
    res <<= 20;
    res -= arg * int64_t(48576);
    return res;
}

} //namespace internal

//zero-terminated string
template<typename Output>
size_t writeStringField(Output * file, const char * data)
{
    uint32_t length = strlen(data);
    writeInteger(file, length);
    writeArray(file, data, length);
    return sizeof(length) + length;
}

template<typename Output>
size_t writeIPField(Output * file, int32_t ip)
{
    /* info from PostgreSQL src/backend/utils/adt/network.c network_send function
        0x02 - ip_family
        0x20 - ip_bits (32)
        0x00 - is_cidr
        0x04 - nb (length?) */
    static const char ipHeader[] = {0x02, 0x20, 0x00, 0x04};

    int32_t length = arraysize(ipHeader) + sizeof(int32_t);
    writeInteger(file, length);
    writeArray(file, ipHeader, arraysize(ipHeader));
    writeInteger(file, ip);
    return sizeof(ip) + sizeof(length) + arraysize(ipHeader);
}

template<typename Output>
size_t writeTableHeader(Output * file)
{
    static const char tableHeader[] = {'\x50', '\x47', '\x43', '\x4f',
                                       '\x50', '\x59', '\x0a', '\xff',
                                       '\x0d', '\x0a', '\x00', '\x00',
                                       '\x00', '\x00', '\x00', '\x00',
                                       '\x00', '\x00', '\x00'};

    writeArray(file, tableHeader, arraysize(tableHeader));
    return arraysize(tableHeader);
}

template<typename Output>
size_t writeRowHeader(Output * file, int16_t fieldsCount)
{
    writeInteger(file, fieldsCount);
    return sizeof(fieldsCount);
}

template<typename Output>
size_t writeTableTrailer(Output * file)
{
    static const char tableTrailer[] = {'\xff', '\xff'};
    writeArray(file, tableTrailer, arraysize(tableTrailer));
    return arraysize(tableTrailer);
}

template<typename Output>
size_t writeTimestampField(Output * file, int32_t seconds, int32_t microseconds)
{
    //seconds between Unix Epoch Time (1970-01-01 00:00:00) and 2000-01-01 00:00:00
    const static int32_t postgresEpochTime = 946674000;
    writeIntegerField(file, static_cast<int64_t>(mul1M(seconds - postgresEpochTime, 0) + microseconds));
    return sizeof(int64_t);
}

template<typename Output, typename Data>
size_t writeIntegerField(Output * file, Data data)
{
    writeInteger(file, static_cast<uint32_t>(sizeof(data)));
    writeInteger(file, data);
    return sizeof(uint32_t) + sizeof(data);
}

} // namespace pgwb

#endif // PGWB_H
